/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年5月23日
 */
package com.massyframework.beanskin.util.version;

import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * {@code Version}, 版本的区分标识符.
 * <ul>
 * <li>Major version: 主要版本，非负整数.</li>
 * <li>Minor version: 次要版本，非负整数.</li>
 * <li>Micro version: 修订版本，非负整数.</li>
 * <li>Qualifier: 限定符，文本字符串，</li>
 * </ul>
 * 
 * <p>
 * 摘抄来自osgi的Version实现.
 *
 */
public final class Version {

	private final int			major;
    private final int			minor;
    private final int			micro;
    private final String		qualifier;
    private static final String	SEPARATOR		= ".";
    private transient String	versionString   /* 缺省值:  null */;
    private transient int		hash /* 缺省值: 0 */;

    /**
     * 空版本: "0.0.0".
     */
    public static final Version	emptyVersion	= new Version(0, 0, 0);

    /**
     * 用特定的数字创建一个版本标识符。
     * <p>
     * 调用{@link #Version(int, int, int, String)}方法，将限定符设置为空值。
     *
     * @param major {@link int}, 主要版本.
     * @param minor {@link int}, 次要版本.
     * @param micro {@link int}, 修订版本.
     * @throws IllegalArgumentException 无效的数字(负数)时抛出的例外.
     */
    public Version(int major, int minor, int micro) {
        this(major, minor, micro, null);
    }

    /**
     * 用特定的数字和限定符创建一个版本标识符
     *
     * @param major {@link int}, 主要版本.
     * @param minor {@link int}, 次要版本.
     * @param micro {@link int}, 修订版本.
     * @param qualifier {@link String}, 限定符，如果为<code>null</code>那么限定符将被设置为空字符串。
     * @throws IllegalArgumentException 无效的数字(符数)或者限定符无效时抛出例外.
     */
    public Version(int major, int minor, int micro, String qualifier) {
        if (qualifier == null) {
            qualifier = "";
        }

        this.major = major;
        this.minor = minor;
        this.micro = micro;
        this.qualifier = qualifier;
        validate();
    }

    /**
     * 使用特定的版本字符串创建一个版本标识符
     * <p>
     * 版本字符串应该符合以下语法:
     *
     * <pre>
     * version ::= major('.'minor('.'micro('.'qualifier)?)?)?
     * major ::= digit+
     * minor ::= digit+
     * micro ::= digit+
     * qualifier ::= (alpha|digit|'_'|'-')+
     * digit ::= [0..9]
     * alpha ::= [a..zA..Z]
     * </pre>
     *
     * @param version {@link String} 版本字符串，必须没有空格.
     * @throws IllegalArgumentException {@code version}不满足格式要求抛出的例外.
     */
    public Version(String version) {
        int maj = 0;
        int min = 0;
        int mic = 0;
        String qual = "";

        try {
            StringTokenizer st = new StringTokenizer(version, SEPARATOR, true);
            maj = parseInt(st.nextToken(), version);

            if (st.hasMoreTokens()) { // minor
                st.nextToken(); // consume delimiter
                min = parseInt(st.nextToken(), version);

                if (st.hasMoreTokens()) { // micro
                    st.nextToken(); // consume delimiter
                    mic = parseInt(st.nextToken(), version);

                    if (st.hasMoreTokens()) { // qualifier separator
                        st.nextToken(); // consume delimiter
                        qual = st.nextToken(""); // remaining string

                        if (st.hasMoreTokens()) { // fail safe
                            throw new IllegalArgumentException("invalid version \"" + version + "\": invalid format");
                        }
                    }
                }
            }
        } catch (NoSuchElementException e) {
            IllegalArgumentException iae = new IllegalArgumentException("invalid version \"" + version + "\": invalid format");
            iae.initCause(e);
            throw iae;
        }

        major = maj;
        minor = min;
        micro = mic;
        qualifier = qual;
        validate();
    }

    /**
     * 解析字符串数字为整型
     *
     * @param value {@link String},字符串数字
     * @param version {@link String},如果解析发生例外，则异常消息所需的信息。
     * @return {@link int}
     */
    private static int parseInt(String value, String version) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            IllegalArgumentException iae = new IllegalArgumentException("invalid version \"" + version + "\": non-numeric \"" + value + "\"");
            iae.initCause(e);
            throw iae;
        }
    }

    /**
     * 构造方法中对版本进行验证
     *
     * @throws IllegalArgumentException 数字(负数)或者限定符无效时抛出的例外
     */
    private void validate() {
        if (major < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + major + "\"");
        }
        if (minor < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + minor + "\"");
        }
        if (micro < 0) {
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": negative number \"" + micro + "\"");
        }
        for (char ch : qualifier.toCharArray()) {
            if (('A' <= ch) && (ch <= 'Z')) {
                continue;
            }
            if (('a' <= ch) && (ch <= 'z')) {
                continue;
            }
            if (('0' <= ch) && (ch <= '9')) {
                continue;
            }
            if ((ch == '_') || (ch == '-')) {
                continue;
            }
            throw new IllegalArgumentException("invalid version \"" + toString0() + "\": invalid qualifier \"" + qualifier + "\"");
        }
    }

    /**
     * 解析版本字符串并返回{@link Version}
     *
     * <p>
     * 了解版本字符串规范，可查看{@link #Version(String)} .
     *
     * @param version {@link String},版本字符串，必须没有空格.
     * @return 一个{@code Version}实例，如果{@code version}为null，则返回{@link #emptyVersion}.
     * @throws IllegalArgumentException {@code version}不满足格式要求抛出的例外.
     */
    public static Version parseVersion(String version) {
        if (version == null) {
            return emptyVersion;
        }

        return valueOf(version);
    }

    /**
     * 返回 {@code Version}实例。
     *
     * <p>
     * 了解版本字符串规范，可查看{@link #Version(String)} .
     *
     * <p>
     * 这个方式和{@link #parseVersion(String)}类似。
     *
     * @param version {@link String},版本字符串，必须没有空格且不为null.
     * @return 一个{@code Version}实例，如果{@code version}为空字符串，则返回{@link #emptyVersion}.
     * @throws IllegalArgumentException {@code version}不满足格式要求抛出的例外.
     */
    public static Version valueOf(String version) {
        version = version.trim();
        if (version.length() == 0) {
            return emptyVersion;
        }

        return new Version(version);
    }

    /**
     * 标识符的主版本号
     *
     * @return {@link int}
     */
    public int getMajor() {
        return major;
    }

    /**
     * 标识符的次版本号
     *
     * @return {@link int}.
     */
    public int getMinor() {
        return minor;
    }

    /**
     * 修订版本号
     *
     * @return {@link int}.
     */
    public int getMicro() {
        return micro;
    }

    /**
     * 限定符
     *
     * @return {@link String}.
     */
    public String getQualifier() {
        return qualifier;
    }

    /**
     * 版本的字符串表达形式
     *
     * <p>
     * 如果限定符为空字符串，则返回{@code major.minor.micro}
     *
     * @return {@link String}.
     */
    @Override
    public String toString() {
        return toString0();
    }

    /**
     * 内部的toString实现
     *
     * @return {@link String}
     */
    String toString0() {
        String s = versionString;
        if (s != null) {
            return s;
        }
        int q = qualifier.length();
        StringBuffer result = new StringBuffer(20 + q);
        result.append(major);
        result.append(SEPARATOR);
        result.append(minor);
        result.append(SEPARATOR);
        result.append(micro);
        if (q > 0) {
            result.append(SEPARATOR);
            result.append(qualifier);
        }
        return versionString = result.toString();
    }

    /**
     * hash code.
     *
     * @return {@link int}
     */
    @Override
    public int hashCode() {
        int h = hash;
        if (h != 0) {
            return h;
        }
        h = 31 * 17;
        h = 31 * h + major;
        h = 31 * h + minor;
        h = 31 * h + micro;
        h = 31 * h + qualifier.hashCode();
        return hash = h;
    }

    /**
     * 与{@code object}进行比较是否相同。
     *
     * <p>
     * 如果两个版本相同，需要满足：
     * major.minor.micro两者相同；
     * qualifier满足{@link String#equals(Object)}。
     *
     * @param object 另一个{@link Version}对象实例
     * @return {@link boolean}
     */
    @Override
    public boolean equals(Object object) {
        if (object == this) {
            return true;
        }

        if (!(object instanceof Version)) {
            return false;
        }

        Version other = (Version) object;
        return (major == other.major) && (minor == other.minor) && (micro == other.micro) && qualifier.equals(other.qualifier);
    }

    /**
     * 与另一个{@code other}进行比较
     *      *
     * <p>
     * 如果一个版本小于另一个版本，需要满足：
     * 主要版本号小于另一个版本的主要版本号；
     * 如果主要版本号相同，则次要版本号小于另一个版本的次要版本号；
     * 如果主要版本和次要版本都相同，则修订版本号小于另一个版本的修订版本号；
     * 如果主要版本、次要版本和修订版本都相同，则限定符使用{@link String#compareTo(String)}比较，要小于另一个版本。
     * 
     *
     * @param other {@code Version}，需要比较的另一个版本对象
     * @return {@link int}, 只能取值[-1, 0, 1],分别表示小于，等于和大于{@code other}的关系。
     * @throws ClassCastException 如果{@code other}不是{@link Version}对象实例抛出的例外.
     */
    public int compareTo(Version other) {
        if (other == this) { 
            return 0;
        }

        int result = major - other.major;
        if (result != 0) {
            return result;
        }

        result = minor - other.minor;
        if (result != 0) {
            return result;
        }

        result = micro - other.micro;
        if (result != 0) {
            return result;
        }

        return qualifier.compareTo(other.qualifier);
    }
}
